gdk: Allow internal management of source-side DnD
authorCarlos Garnacho <carlosg@gnome.org>
Fri, 8 Jan 2016 20:03:01 +0000 (21:03 +0100)
committerCarlos Garnacho <carlosg@gnome.org>
Tue, 19 Jan 2016 13:17:35 +0000 (14:17 +0100)
We've traditionally left GTK+ to handle the input side of things,
letting GDK only manage the windowing-specific messaging. This
way of splitting responsibilities is not compatible however with
some backends, we must fold then input management at the DnD stage
into GDK (and backends) domain.

The gdk_drag_context_manage_dnd() call is meant to be the entry
point for this mode of operation, if the drag and drop operation
becomes managed, the caller (i.e. gtkdnd.c) doesn't need to perform
grabs, nor manage input events itself.

As a consequence of this, different aspects now belong to the
backend GdkDragContext implementation:
- Because the caller doesn't see keyboard events anymore,
  keyboard navigation must be managed in GDK, so is the decision
  of the current action based on modifiers/button pressed.
- Because the caller won't see input events in general, the lifetime
  of the drag and drop operation is now communicated through the
  ::drop-performed, ::dnd-finished and ::cancel events
- Because the caller doesn't participate anymore on the action
  being chosen, the pointer cursor must be set by the backend.
  The caller is rather notified of the final action through the
  ::action signal.

The caller is still responsible of dealing with the corresponding
GdkSelection, ensuring its ownership and communicating the supported
mimetypes.

gdk/gdkdnd.c
gdk/gdkdnd.h
gdk/gdkdndprivate.h

index 30096302f321d95395e12407a040e4280e1b0d7e..3a8861a188b4b2d737e0aba72838244ed4ebcea6 100644 (file)
 #include "gdkdndprivate.h"
 #include "gdkdisplay.h"
 #include "gdkwindow.h"
+#include "gdkintl.h"
+#include "gdkenumtypes.h"
 
+enum {
+  CANCEL,
+  DROP_PERFORMED,
+  DND_FINISHED,
+  ACTION_CHANGED,
+  N_SIGNALS
+};
+
+static guint signals[N_SIGNALS] = { 0 };
+static GList *contexts = NULL;
 
 /**
  * SECTION:dnd
@@ -217,6 +229,7 @@ G_DEFINE_TYPE (GdkDragContext, gdk_drag_context, G_TYPE_OBJECT)
 static void
 gdk_drag_context_init (GdkDragContext *context)
 {
+  contexts = g_list_prepend (contexts, context);
 }
 
 static void
@@ -224,6 +237,7 @@ gdk_drag_context_finalize (GObject *object)
 {
   GdkDragContext *context = GDK_DRAG_CONTEXT (object);
 
+  contexts = g_list_remove (contexts, context);
   g_list_free (context->targets);
 
   if (context->source_window)
@@ -241,6 +255,90 @@ gdk_drag_context_class_init (GdkDragContextClass *klass)
   GObjectClass *object_class = G_OBJECT_CLASS (klass);
 
   object_class->finalize = gdk_drag_context_finalize;
+
+  /**
+   * GdkDragContext::cancel:
+   *
+   * The drag and drop operation was cancelled.
+   *
+   * This signal will only be emitted if the #GdkDragContext manages
+   * the drag and drop operation. See gdk_drag_context_manage_dnd()
+   * for more information.
+   *
+   * Since: 3.20
+   */
+  signals[CANCEL] =
+    g_signal_new (P_("cancel"),
+                  G_TYPE_FROM_CLASS (object_class),
+                  G_SIGNAL_RUN_FIRST,
+                  G_STRUCT_OFFSET (GdkDragContextClass, cancel),
+                  NULL, NULL,
+                  g_cclosure_marshal_VOID__VOID,
+                  G_TYPE_NONE, 0);
+
+  /**
+   * GdkDragContext::drop-performed:
+   * @time: the time at which the drop happened.
+   *
+   * The drag and drop operation was performed on an accepting client.
+   *
+   * This signal will only be emitted if the #GdkDragContext manages
+   * the drag and drop operation. See gdk_drag_context_manage_dnd()
+   * for more information.
+   *
+   * Since: 3.20
+   */
+  signals[DROP_PERFORMED] =
+    g_signal_new (P_("drop-performed"),
+                  G_TYPE_FROM_CLASS (object_class),
+                  G_SIGNAL_RUN_LAST,
+                  G_STRUCT_OFFSET (GdkDragContextClass, drop_performed),
+                  NULL, NULL,
+                  g_cclosure_marshal_VOID__INT,
+                  G_TYPE_NONE, 1, G_TYPE_INT);
+
+  /**
+   * GdkDragContext::dnd-finished:
+   *
+   * The drag and drop operation was finished, the drag destination
+   * finished reading all data. The drag source can now free all
+   * miscellaneous data.
+   *
+   * This signal will only be emitted if the #GdkDragContext manages
+   * the drag and drop operation. See gdk_drag_context_manage_dnd()
+   * for more information.
+   *
+   * Since: 3.20
+   */
+  signals[DND_FINISHED] =
+    g_signal_new (P_("dnd-finished"),
+                  G_TYPE_FROM_CLASS (object_class),
+                  G_SIGNAL_RUN_LAST,
+                  G_STRUCT_OFFSET (GdkDragContextClass, dnd_finished),
+                  NULL, NULL,
+                  g_cclosure_marshal_VOID__VOID,
+                  G_TYPE_NONE, 0);
+
+  /**
+   * GdkDragContext::action-changed:
+   * @action: The action currently chosen
+   *
+   * A new action is being chosen for the drag and drop operation.
+   *
+   * This signal will only be emitted if the #GdkDragContext manages
+   * the drag and drop operation. See gdk_drag_context_manage_dnd()
+   * for more information.
+   *
+   * Since: 3.20
+   */
+  signals[ACTION_CHANGED] =
+    g_signal_new (P_("action-changed"),
+                  G_TYPE_FROM_CLASS (object_class),
+                  G_SIGNAL_RUN_LAST,
+                  G_STRUCT_OFFSET (GdkDragContextClass, action_changed),
+                  NULL, NULL,
+                  g_cclosure_marshal_VOID__FLAGS,
+                  G_TYPE_NONE, 1, GDK_TYPE_DRAG_ACTION);
 }
 
 /**
@@ -527,3 +625,90 @@ gdk_drag_drop_done (GdkDragContext *context,
   if (GDK_DRAG_CONTEXT_GET_CLASS (context)->drop_done)
     GDK_DRAG_CONTEXT_GET_CLASS (context)->drop_done (context, success);
 }
+
+/**
+ * gdk_drag_context_manage_dnd:
+ * @context: a #GdkDragContext
+ * @ipc_window: Window to use for IPC messaging/events
+ * @actions: the actions supported by the drag source
+ *
+ * Requests the drag and drop operation to be managed by @context.
+ * When a drag and drop operation becomes managed, the #GdkDragContext
+ * will internally handle all input and source-side #GdkEventDND events
+ * as required by the windowing system.
+ *
+ * Once the drag and drop operation is managed, the drag context will
+ * emit the following signals:
+ * - The #GdkDragContext::action-changed signal whenever the final action
+ *   to be performed by the drag and drop operation changes.
+ * - The #GdkDragContext::drop-performed signal after the user performs
+ *   the drag and drop gesture (typically by releasing the mouse button).
+ * - The #GdkDragContext::dnd-finished signal after the drag and drop
+ *   operation concludes (after all #GdkSelection transfers happen).
+ * - The #GdkDragContext::cancel signal if the drag and drop operation is
+ *   finished but doesn't happen over an accepting destination, or is
+ *   cancelled through other means.
+ *
+ * Returns: #TRUE if the drag and drop operation is managed.
+ **/
+gboolean
+gdk_drag_context_manage_dnd (GdkDragContext *context,
+                             GdkWindow      *ipc_window,
+                             GdkDragAction   actions)
+{
+  g_return_val_if_fail (GDK_IS_DRAG_CONTEXT (context), FALSE);
+  g_return_val_if_fail (GDK_IS_WINDOW (ipc_window), FALSE);
+
+  if (GDK_DRAG_CONTEXT_GET_CLASS (context)->manage_dnd)
+    return GDK_DRAG_CONTEXT_GET_CLASS (context)->manage_dnd (context, ipc_window,
+                                                             actions);
+
+  return FALSE;
+}
+
+void
+gdk_drag_context_set_cursor (GdkDragContext *context,
+                             GdkCursor      *cursor)
+{
+  g_return_if_fail (GDK_IS_DRAG_CONTEXT (context));
+
+  if (GDK_DRAG_CONTEXT_GET_CLASS (context)->set_cursor)
+    GDK_DRAG_CONTEXT_GET_CLASS (context)->set_cursor (context, cursor);
+}
+
+void
+gdk_drag_context_cancel (GdkDragContext *context)
+{
+  g_return_if_fail (GDK_IS_DRAG_CONTEXT (context));
+
+  g_signal_emit (context, signals[CANCEL], 0);
+}
+
+GList *
+gdk_drag_context_list (void)
+{
+  return contexts;
+}
+
+gboolean
+gdk_drag_context_handle_source_event (GdkEvent *event)
+{
+  GdkDragContext *context;
+  GList *l;
+
+  for (l = contexts; l; l = l->next)
+    {
+      context = l->data;
+
+      if (!context->is_source)
+        continue;
+
+      if (!GDK_DRAG_CONTEXT_GET_CLASS (context)->handle_event)
+        continue;
+
+      if (GDK_DRAG_CONTEXT_GET_CLASS (context)->handle_event (context, event))
+        return TRUE;
+    }
+
+  return FALSE;
+}
index 1aa5d070922eaa33a53802d0d9151de132e1cf69..65c8c873f2a4d838d39e135891e0d98c7e224533 100644 (file)
@@ -190,6 +190,10 @@ void            gdk_drag_context_set_hotspot (GdkDragContext *context,
                                               gint            hot_x,
                                               gint            hot_y);
 
+GDK_AVAILABLE_IN_3_20
+gboolean        gdk_drag_context_manage_dnd (GdkDragContext *context,
+                                             GdkWindow      *ipc_window,
+                                             GdkDragAction   actions);
 G_END_DECLS
 
 #endif /* __GDK_DND_H__ */
index fe2a95dfc758516599c0cc64d83e633d05bfb272..2404293b55a5caf55570a73dc57940d381fb9c16 100644 (file)
@@ -68,6 +68,21 @@ struct _GdkDragContextClass {
                                 gint             hot_y);
   void        (*drop_done)     (GdkDragContext   *context,
                                 gboolean          success);
+
+  gboolean    (*manage_dnd)     (GdkDragContext  *context,
+                                 GdkWindow       *ipc_window,
+                                 GdkDragAction    actions);
+  void        (*set_cursor)     (GdkDragContext  *context,
+                                 GdkCursor       *cursor);
+  void        (*cancel)         (GdkDragContext  *context);
+  void        (*drop_performed) (GdkDragContext  *context,
+                                 guint32          time);
+  void        (*dnd_finished)   (GdkDragContext  *context);
+
+  gboolean    (*handle_event)   (GdkDragContext  *context,
+                                 const GdkEvent  *event);
+  void        (*action_changed) (GdkDragContext  *context,
+                                 GdkDragAction    action);
 };
 
 struct _GdkDragContext {
@@ -91,6 +106,13 @@ struct _GdkDragContext {
   GdkDevice *device;
 };
 
+GList *  gdk_drag_context_list (void);
+
+void     gdk_drag_context_set_cursor          (GdkDragContext *context,
+                                               GdkCursor      *cursor);
+void     gdk_drag_context_cancel              (GdkDragContext *context);
+gboolean gdk_drag_context_handle_source_event (GdkEvent *event);
+
 G_END_DECLS
 
 #endif